
(function($) {
	var _CELL_LINE_WIDTH = 1;
	var _SCROLL_WIDTH = 16;
	var _HEADER_HEIGHT = 24;
	var _ROW_HEIGHT = 20;

	/**
	 * 
	 * @module widget
	 * @namespace widget
	 * @class DataGrid
	 */
	DataGrid = G.GClass('Widget', 'DataGrid', G.Base.EventListener);
	
	DataGrid.prototype.__init = function(cols) 
	{
		this.__Base__EventListener__init();

		this.config = {
			selectOnClick: false,	// Select the row on click
			multiSelect:   false	// Support multiselect by Ctrl+click
		}
		this.cols = cols;
		this.dragEventHandler = null;
		this.dragEventObj = null;

		this.pageSize = 100;

		this.dataSource = null;

		/* =============Data Source
		 *    |
		 *    |
		 *    |------> Data Offset
		 *    |    |
		 *    |    |
		 *    |    |--> ViewOffset
		 *    |    | 
		 *    |    | Viewable area >= viewOffset and < ViewEnd.
		 *    |    |
		 *    |     --> ViewEnd
		 *    | 
		 *    |------> End of current page.
		 *    |
		 *  =============
		 */
		this.dataOffset = 0;	// start of data
		this.viewOffset = 0;	// The first row located in view area.
		this.viewEnd = 0;		// The last row located in view area.
		this.backRowCount = 1;

		this.rowCount = 0;	// How many rows in this page
		this.scrollOffset = 0;	// scroll offset in page 

		this.rows = [];			// row element displayed in the view area.
		this.currentRow = -1;	// Current row in the whole page.
		this.rowStatus = [];	// Save the select status of ver rows in page.

		this.selectIndicatorCol = -1;	// Which col is the checkbox for selection.

		this.rowHeight = 0;	// The height will be set when first row created.
		this.scrollX = 0;

		this.handlerList = {};
	}

	/**
	 * Link the grid to a data source. the datagird will show the first page of
	 * data on data ready.
	 * @method link_data
	 */
	DataGrid.prototype.link_data = function(dataSource) 
	{
		this.dataSource = dataSource;
		this.dataSource.bind("ondataready", this._on_data_ready, this);
		this.dataSource.bind("ontotalchanged", this._on_total_changed, this);
		this.dataSource.get_total();
		this.go(0);
	}

	/**
	 * Remove all rows in body. and reset the scrollbar
	 * @method clear_view
	 */
	DataGrid.prototype.clear_view = function() 
	{
		this.divBody.innerHTML = "";
		// Reset the scroll bar
		this.scbV.reset(0, 100, 100);
		this.rows = [];
		this.rowStatus = [];

		// clear view
		this.viewEnd = 0;
		this.viewOffset = 0;
		this.rowCount = 0;
	}

	/* ==========================================================
	 * Paged Browse functions 
	 * When switch between pages, the whole body of datagrid will 
	 * be clear and redraw.  the row status will be cleared to
	 * non-selection.
	 * ==========================================================
	 */

	/**
	 * Get current page number. The number of the first page is 1.
	 * @method current_page
	 * @return {integer} the page number
	 */
	DataGrid.prototype.current_page = function() 
	{
		return Math.floor(this.dataOffset  / this.pageSize) + 1;
	}

	/**
	 * Get total pages of all data.
	 * @method page_total
	 * @return {integer} 
	 */
	DataGrid.prototype.page_total = function() 
	{
		var len = this.dataTotal;
		if (len < 1) {
			return 1;
		}
		return (Math.floor((len - 1) / this.pageSize)) + 1;
	}

	/**
	 * Show next page of data if there are more after this page
	 * @method next_page
	 */
	DataGrid.prototype.next_page = function() 
	{
		var pg = this.current_page();
		var offset = pg * this.pageSize;
		if (offset >= this.dataTotal) {
			return;
		}
		this.go(offset);
	}

	/**
	 * Show previous page if it's not at begining of data.
	 * @method prev_page
	 */
	DataGrid.prototype.prev_page = function() 
	{
		var pg = this.current_page();
		if (pg > 1) {
			pg -= 1;
			offset = (pg - 1) * this.pageSize;
			this.go(offset);
		}
	}

	/**
	 * Show the last page of data.
	 * @method last_page
	 */
	DataGrid.prototype.last_page = function() 
	{
		var pg = this.page_total() - 1;
		var offset = pg * this.pageSize;
		this.go(offset);
	}

	/**
	 * Show the first page of data
	 * @method first_page
	 */
	DataGrid.prototype.first_page = function() 
	{
		this.go(0);
	}

	/**
	 * Set the page size. the datagrid will jump to the first page
	 * after the pagesize changed.
	 * @method set_page_size
	 * @param size {integer}
	 */
	DataGrid.prototype.set_page_size = function(size)
	{
		this.pageSize = size;
		if (this.dataSource) {
			this.go(0);
		}
	}

	/**
	 * Get a page of data from data source at offset. and show it when data arrived.
	 * @method go
	 * @param offset {integer}
	 */
	DataGrid.prototype.go = function(offset) 
	{
		this.dataSource.get_data(offset, this.pageSize);
	}

	DataGrid.prototype._on_total_changed = function(total)
	{
		this.dataTotal = total;
	}


	DataGrid.prototype._on_data_ready = function(datapos) 
	{
		this.dataOffset = datapos.offset;
		this.scroll = 0;
		this.divCurrentPage.value = this.current_page() + "/" + this.page_total();
		this.update();
	}

	/* ==========================================================
	 * Movement in page area.
	 * ==========================================================
	 */
	/**
	 * Highlight a row as current. The view area will scroll up ro down if the row
	 * is out of it.
	 *
	 * @method set_current
	 * @param curr {integer} number of the row. the number of first row is 0
	 */
	DataGrid.prototype.set_current = function(curr) 
	{
		var old = this.currentRow;

		if (curr == old)	return;

		if ((old >= this.viewOffset) && (old < this.viewEnd)) {
			this._update_row_view(this.rows[old - this.viewOffset], this.rowStatus[old]);
		}

		this.currentRow = curr;

		if (curr == this.viewOffset) {
			// If the first line was partly displayed, change the offset to show all of it.
			if (this.scrollOffset > 0) {
				offset = this.viewOffset * this.rowHeight;
				console.log("set offset: " + offset);
				this.scbV.set_offset(offset, true);
			} else {
				this.rows[0].className = "current";
			}
		} else if (curr < this.viewOffset) {
			offset = curr * this.rowHeight;
			this.scbV.set_offset(offset, true);
		} else {
			var h = (curr - this.viewOffset + 1) * this.rowHeight;
			if (h > this.viewHeight) {
				offset = ((curr + 1) * this.rowHeight) - this.viewHeight;
				this.scbV.set_offset(offset, true);
			} else {
				this.rows[curr - this.viewOffset].className = "current";
			}
		}
		this.fire("oncurrentchanged", {old: old, current: curr});
	}


	/*
	 * Scroll the view area in page, to make the offset at the top of view area. 
	 */
	DataGrid.prototype._scroll_in_page = function(offset) 
	{

		if (this.rows.length < 1)	return;

		var offsetMax = this.scrollHeight - this.viewHeight;
		if (offset > offsetMax)	offset = offsetMax;

		var h = this.rowHeight;
		var viewOffset = Math.floor(offset / h);			// Start of row
		var scrollOffset = offset % h;						// scroll in line
		var viewCount = Math.floor((scrollOffset + this.viewHeight + h - 1) / h);	// row count need be showed in view.
		this.scrollOffset = scrollOffset;
		var viewEnd = viewOffset + viewCount;

		if (viewEnd > this.rowCount) {
			viewEnd = this.rowCount;
			viewCount = viewEnd - viewOffset;
		}

		var appendStart = 0;
		var appendEnd = 0;
		if ((viewEnd <= this.viewOffset) || (viewOffset >= this.viewEnd)) {
			this.divBody.innerHTML = "";
			this.rows = [];

			appendStart = viewOffset;
			appendEnd = viewEnd;
		} else {
			for (var i = this.viewOffset; i < viewOffset; i++) {
				this.divBody.removeChild(this.rows.shift());
			}
			for (var i = this.viewEnd - viewEnd; i > 0; i--) {
				this.divBody.removeChild(this.rows.pop());
			}

			for (var i = (this.viewOffset - 1); i >= viewOffset; i--) {
				var dat = this.dataSource.read(i);
				if (!dat) break;
				var tr = this._create_row(i, dat);
				this.divBody.insertBefore(tr, this.rows[0]);
				this.rows.unshift(tr);
			}
			appendStart = this.viewEnd;
			appendEnd = viewEnd;
		}

		for (var i = appendStart; i < appendEnd; i++) {
			var dat = this.dataSource.read(i); 
			var tr = this._create_row(i, dat);
			this.divBody.appendChild(tr);
			this.rows.push(tr);
		}

		this.viewOffset = viewOffset;
		this.viewEnd = viewEnd;
		this.divBodyField.style.marginTop = 0 - scrollOffset;


		/* Make sure background have enough rows to full fill the body */
		var i = this.backRowCount;
		var cnt = viewEnd - viewOffset;

		if (i != cnt) {
			for (; i > cnt; i--) {
				this.divBodyBack.removeChild(this.divBodyBack.childNodes[i-1]);
			}
			for (; i < cnt; i++) {
				var div = document.createElement('div');
				div.className = "dgrid_row_back" + (i & 0x01);
				this.divBodyBack.appendChild(div);
			}
			this.backRowCount = i;
		}
		this._update_row_status();
	}


	DataGrid.prototype._create_row = function(rowIndex, dat) 
	{
		var tr;
		if (this.rows.length > 0) {
			tr = this.rows[0].cloneNode(true);
			tr.removeAttribute("className");
		} else {
			tr = this.divHead.cloneNode(true);
			tr.removeAttribute("className");
			tr.removeAttribute("style");
		}
		var datIndex = rowIndex + this.dataOffset;

		for (var i = 0; i < this.cols.length; i++) {
			var col = this.cols[i];
			var cell = tr.childNodes[i];

			if (col.type == 'data') {
				if (typeof(col.format) == 'function') {
					col.format(cell, dat);	
				} else {
					cell.innerHTML = dat[col.key];
				}
			} else if (col.type == 'index') {
				cell.innerHTML = datIndex + 1;
			}
		}

		var st = this.rowStatus[rowIndex];
		this._update_row_view(tr, st);
		return tr;
	}



	DataGrid.prototype._create_nav = function() 
	{
		var divNavView = document.createElement('div');
		divNavView.className = "dgrid_nav";

		var divFirstPage = document.createElement('a');
		var divPrevPage = document.createElement('a');
		var divNextPage = document.createElement('a');
		var divLastPage = document.createElement('a');
		var divPage = document.createElement('input');

		divFirstPage.className = "dgrid_btn_first";
		divPrevPage.className = "dgrid_btn_prev";
		divNextPage.className = "dgrid_btn_next";
		divLastPage.className = "dgrid_btn_last";

		divFirstPage.href = "#";
		divPrevPage.href = "#";
		divNextPage.href = "#";
		divLastPage.href = "#";

		divNavView.appendChild(divLastPage);
		divNavView.appendChild(divNextPage);
		divNavView.appendChild(divPage);
		divNavView.appendChild(divPrevPage);
		divNavView.appendChild(divFirstPage);

		divPage.value = 0;
		this.divPage = divPage;
		this.divNavView = divNavView;
		this.divCurrentPage = divPage;
		this.divGrid.appendChild(divNavView);

		this.btnFirstPage = G(divFirstPage);
		this.btnPrevPage = G(divPrevPage);
		this.btnNextPage = G(divNextPage);
		this.btnLastPage = G(divLastPage);

		this.btnFirstPage.bind("onclick", this.first_page, this);
		this.btnPrevPage.bind("onclick", this.prev_page, this);
		this.btnNextPage.bind("onclick", this.next_page, this);
		this.btnLastPage.bind("onclick", this.last_page, this);
	}

	DataGrid.prototype._create_head = function() 
	{
		var w = 0;
		for (var i in this.cols) {
			var col = this.cols[i];
			var cell = document.createElement('div');
			cell.style.width = col.width;

			if (col.type == 'select') {
				var chkSelectAll = document.createElement('input');
				chkSelectAll.type="checkbox";
				this.selectIndicatorCol = i;
				cell.appendChild(chkSelectAll);
			} else {
				cell.innerHTML = col.name;
			}

			this.divHead.appendChild(cell);
			w += cell.offsetWidth;
		}
		this.divHead.style.width = w;
		this.divBody.style.width = w;
		this.divBodyBack.style.width = w;
	}

	DataGrid.prototype._create_body_back = function() 
	{
		for (var i = 0; i < this.backRowCount; i++) {
			var div = document.createElement('div');
			div.className = "dgrid_row_back" + (i & 0x01);
			this.divBodyBack.appendChild(div);
		}

	}


	DataGrid.prototype._resize = function(w, h) 
	{
		this.divGrid.style.width = w;
		this.divGrid.style.height = h;

		w -= this.divScrollY.offsetWidth;
		this.divScrollY.style.left = w;
		this.divScrollY.style.height = h;

		var offsetH = this.divHeadView.offsetHeight + this.divNavView.offsetHeight;
		this.divScrollY.style.top = offsetH;

		h -= offsetH + this.divScrollX.offsetHeight;
		this.divBodyView.style.height = h;
		this.divScrollY.style.height = h;
		this.viewHeight = h;

		this.divScrollX.style.width = w;
		this.divScrollX.left = 0;

		this.scbH.reset(0, this.divHeadView.clientWidth, this.divHead.offsetWidth);
		this.update();

	}

	/*
	 * Recalculate the size of scroll bar
	 */
	DataGrid.prototype._update_scroll_bar_v = function() 
	{
		if (this.rowCount <= 0) {
			this.scrollHeight = 100;
			this.scbv.reset(0, 100, 100);
			return;
		} 
		var h = this.rowCount * this.rowHeight;
		if (h < this.divBodyView.clientHeight) {
			h = this.divBodyView.clientHeight;
		}
		this.scrollHeight = h;

		this.scbV.reset(0, this.divBodyView.clientHeight, h);
		this._scroll_in_page(0);
	}


	DataGrid.prototype.update = function() 
	{
		if (!this.dataSource)	return;

		this.clear_view();
		var rowCount = this.dataSource.get_page_len();

		for (var i = 0; i < rowCount; i++) {

			if (this.divBody.offsetHeight >= this.divBodyView.offsetHeight)	break;
			var dat = this.dataSource.step_in_page();
			if (!dat) break;

			var tr = this._create_row(i, dat);
			this.divBody.appendChild(tr);
			this.rows[i] = tr;
			this.rowHeight = tr.offsetHeight;
		}

		// How many rows displayed in view box.
		this.viewEnd = i;

		if (this.rowCount != rowCount) {
			this.rowCount = rowCount;
			this._update_scroll_bar_v();
		}
	}


	DataGrid.prototype.render = function(em) 
	{
		this.container = em;

		var divGrid = document.createElement('div');
		divGrid.className = "dgrid";
		em.appendChild(divGrid);
		this.divGrid = divGrid;

		this._create_nav();

		// Create Header
		hdView = document.createElement('div');
		hd = document.createElement('div');
		hdView.className = 'dgrid_header';
		hdView.appendChild(hd);
		divGrid.appendChild(hdView);
		
		// Create Body
		// BodyView --> BodyField --> BodyBack + Body
		bdView = document.createElement('div');
		bdField = document.createElement('div');
		bdBack = document.createElement('div');
		bd = document.createElement('div');

		bdView.className = 'dgrid_body_view';
		bdField.className = 'dgrid_body_field';
		bdBack.className = "dgrid_body_back";
		bd.className = "dgrid_body";

		bdField.appendChild(bd);
		bdField.appendChild(bdBack);
		bdView.appendChild(bdField);
		divGrid.appendChild(bdView);


		// Create Scroll Bar.
		scy = document.createElement('div');
		scy.className = 'dgrid_scroll_y';
		scx = document.createElement('div');
		scx.className = 'dgrid_scroll_x';
		divGrid.appendChild(scy);
		divGrid.appendChild(scx);

		this.divHead = hd;
		this.divHeadView = hdView;
		this.divBody = bd;
		this.divBodyView = bdView;
		this.body = G(bdView);
		this.divBodyField = bdField;
		this.divBodyBack = bdBack;
		this.divScrollY= scy;
		this.divScrollX= scx;

		// Draw col headers
		this._create_head();

		this._create_body_back();

		// Create scroll bar.
		this.scbV = G(scy).ScrollBar('V');
		this.scbH = G(scx).ScrollBar('H');

		this.scbH.bind("onscroll", function(ev){
			this.divHead.style.marginLeft = -ev.offset;
			this.divBodyField.style.marginLeft = -ev.offset;
			this.scrollX = ev.offset;
		}, this);

		this.scbV.bind("onscroll", function(ev) {
			this._scroll_in_page(ev.offset);
		}, this);

		this.body.bind("onclick", this._on_body_click, this);
		console.log("bind keypress");
		this.body.bind("onkeypress", this._on_keypress, this);

		this._resize(this.container.clientWidth, this.container.clientHeight);

		G(this.container).bind("onresize", function(ev) {
			this._resize(ev.w, ev.h);
		}, this);
	}

	/* 
	 * Get the row and col number of the the cell the point locate in.
	 * */
	DataGrid.prototype.get_cell_from_position = function(x, y) 
	{
		var row = Math.floor((y + this.scrollOffset) / this.rowHeight);
		row += this.viewOffset;

		x += this.scrollX;

		var cells = this.divHead.childNodes;
		var len = cells.length;
		for (i = 0; i < len; i++) {
			x -= cells[i].offsetWidth;
			if (x < 0)	break;
		}
		return {row: row, col: i};
	}
	

	DataGrid.prototype._on_body_click = function(ev) 
	{
		var orig = this.body.get_offset_in_page();
		var pos = G(document).get_mouse(ev);
		var y = pos.y - orig.top;
		var x = pos.x - orig.left;

		var cell = this.get_cell_from_position(x, y);

		if ((this.config.selectOnClick) || (cell.col == this.selectIndicatorCol)) {
			this.switch_select(cell.row);
		}
		this.set_current(cell.row);
	}

	DataGrid.prototype._on_keypress = function(ev)
	{
		console.log(ev.keyCode);
	}


	DataGrid.prototype.switch_select = function(rowIndex) 
	{
		var selected = true;
		if (rowIndex < this.rowCount) {
			if (typeof(this.rowStatus[rowIndex]) != "object") {
				this.rowStatus[rowIndex] = {
					selected: true,
					marked: false
				}
				selected = true;
			} else {
				selected = !this.rowStatus[rowIndex].selected;
				this.rowStatus[rowIndex].selected = selected;
			}
		}

		if ((rowIndex >= this.viewOffset) && (rowIndex < this.viewEnd)) {
			// Update the background color of the row.
			var r = rowIndex - this.viewOffset;
			this._update_row_view(this.rows[r], this.rowStatus[rowIndex]);
		}
	}


	DataGrid.prototype._update_row_view = function(row, st) 
	{
		var selected = false;
		if ((typeof(st) == "object") && st.selected) {
			row.className = "selected";
			selected = true;
		} else {
			row.removeAttribute("className");
			row.removeAttribute("class");
		}
		if (this.selectIndicatorCol >= 0) {
			var cell = row.childNodes[this.selectIndicatorCol];
			var sel = cell.childNodes[0];
			sel.checked = selected;
		}
	}


	DataGrid.prototype._update_row_status = function() 
	{
		if (this.currentRow >= this.viewOffset && this.currentRow < this.viewEnd) {
			this.rows[this.currentRow - this.viewOffset].className = "current";
		}
	}

	G.DOM.Element.prototype.DataGrid = function(cols) {
		var grid = new DataGrid(cols);
		grid.render(this.em);
		return grid;
	}
})();

